package web

import (
	"gddgame.cc/galaxy/server/web"
	"gddgame.cc/galaxy/utils"

	"reflect"
)

type In = string
type Type = string
type Ctl interface{}
type Middleware = web.Handler

const (
	Body   In = "body"
	Query  In = "query"
	Path   In = "path"
	Form   In = "form"
	Cache  In = "cache"
	Header In = "header"
	File   In = "file"
	Cookie In = "cookie"
	All    In = "all"

	Int     Type = "integer"
	Float   Type = "number"
	String  Type = "string"
	Object  Type = "object"
	Array   Type = "array"
	Boolean Type = "boolean"
	Files   Type = "string"
)

type Param struct {
	Name        string
	Description string
	In          In
	Default     interface{}
	Type        Type
	Array       bool
	Map         bool
	Required    bool
	Deprecated  bool
}

type controllerParse struct {
	v reflect.Value
	t reflect.Type
	i bool
	n int
}

func Controller(params []Param, fns ...Ctl) web.Handler {
	fnReflect := make([]*controllerParse, len(fns))
	for key, fn := range fns {
		fv := reflect.ValueOf(fn)
		ft := fv.Type()
		fnReflect[key] = &controllerParse{
			v: fv,
			t: ft,
			i: ft.IsVariadic(),
			n: ft.NumIn(),
		}
	}
	return web.ControllerHandle(func(context web.Context) interface{} {
		var body map[string]interface{}
		var err error
		var v interface{}
		var args []interface{}
		if params != nil {
			args = make([]interface{}, len(params)+1)
			args[0] = context
			for index, value := range params {
				key := value.Name
				switch value.In {
				case Body:
					if body == nil {
						err = context.BodyJson(&body)
					}
					if err == nil {
						v = body[key]
						if value.Array && v == nil {
							v = []interface{}{}
						}
					}
				case Query:
					if value.Array {
						v, err = context.GetParamArray(key)
					} else if value.Map {
						v, err = context.GetParamMap(key)
					} else {
						v, err = context.GetParam(key)
					}
				case Path:
					v = context.PathParam(key)
				case Cookie:
					c, err := context.Cookie(key)
					if err != nil {
						v = c.Value
					}
				case Cache:
					_ = context.Get(key, &v)
					//fmt.Println(v)
				case File:
					file, header, err := context.FormFile(key)
					if err == nil {
						v = header
						// 系统自动打开，直接关闭
						_ = file.Close()
					} else if file != nil {
						_ = file.Close()
					}
				case Form:
					if value.Array {
						v, err = context.PostParamArray(key)
					} else if value.Map {
						v, err = context.PostParamMap(key)
					} else {
						v, err = context.PostParam(key)
					}
				case All:
					if body == nil {
						err = context.BodyJson(&body)
					}
					v = body
				}
				if value.Required && (v == nil) {
					return ValidExceptionDetail(key+" is required", err)
				}
				if (v == nil || v == "") && value.Default != nil {
					v = value.Default
				} else if value.Array {
					switch value.Type {
					case Int:
						v = utils.ToIntSlice(v)
					case Float:
						v = utils.ToFloat32(v)
					case Boolean:
						v = utils.ToBoolSlice(v)
					case String:
						fallthrough
					default:
						v = utils.ToStringSlice(v)
					}
				} else if !value.Map && value.Type != "" {
					switch value.Type {
					case Int:
						v = utils.ToInt(v)
					case Float:
						v = utils.ToFloat32(v)
					case Boolean:
						v = utils.ToBool(v)
					case String:
						fallthrough
					default:
						v = utils.ToString(v)
					}
				}
				if v == nil && err != nil {
					return err
				}
				args[index+1] = v
			}
		} else {
			args = []interface{}{context}
		}

		context.Debugf("[ Web ] Param:%#v", args)
		result := args
		for _, fn := range fnReflect {
			if !fn.i && len(result) != fn.n {
				return ValidException("parameter mismatched")
			}

			in := make([]reflect.Value, len(result))
			for k, _ := range result {
				fv := reflect.ValueOf(result[k])
				field := fn.t.In(k)
				if result[k] == nil {
					fv = reflect.Zero(field)
				} else if result[k] != nil && field != fv.Type() && (!fn.i || k-1 < fn.n) {
					fv = utils.AutoTo(fv, field)
				}
				in[k] = fv
			}
			value := fn.v.Call(in)
			result = result[:1]
			for i, _ := range value {
				result = append(result, value[i].Interface())
			}
			err, ok := result[len(value)].(error)
			if ok {
				if err != nil {
					return err
				}
			}
			result = result[:len(value)]
		}
		if len(result) == 1 {
			return nil
		} else if len(result) == 2 {
			return result[1]
		} else {
			return result[1:]
		}
	})
}
